/* File:  util.c */

#include <ctype.h>    /* isdigit */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#ifndef snprintf
#define  snprintf _snprintf
#endif
#else
#include <poll.h>     /* poll() */
#include <unistd.h>   /* sleep() */
#endif /* _WIN32 */

#include "ar.h"
#include "get.h"
#include "globals.h"
#include "main.h"
#include "util.h"
#include "print.h"
#include "arerrno.h"
#include "arextern.h"
#include "arstruct.h"
#include "arfree.h"

void BeginAPICall();
void EndAPICall();

#ifndef OUTPUT_MODE
void ProcessResultFile()
{
   /* perform any generic result operations here */
}

ARBoolean ProcessCommentLine(commentLine)
char *commentLine;  /* IN/OUT; comment line */
{
   /* perform any generic comment processing */

   return (TRUE);
}
#endif


/*****************************************************************************/
/*                                                                           */
/*                                GetInputLine                               */
/*                                                                           */
/*****************************************************************************/

void GetInputLine()

{
   int                  bufLen; /* length of the buffer */
   int                  bufOffset; /* offset of the buffer */
   ARStatusList         status = {0, NULL};
   ThreadControlBlock  *threadControlBlockPtr; /* control block pointer */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /*
    * Output might not be a terminal emulator, so stdio doesn't know to
    * flush the output before starting a read.  Help it along here.
    */
   if (threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth] == stdin)
   {
      fflush(stdout);
   }

   if (fgets(threadControlBlockPtr->buffer, DRIVER_INPUT_BUFFER_LEN,
             threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth]) == NULL)
   {
      if (threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth] == stdin)
      {                          /* eof; print error and exit */
         DriverPrintError("\n*** EOF on input file; exiting\n");
         BeginAPICall();
         ARTermination(GetControlStructPtr(), &status);
         EndAPICall(&status);
         exit(1);
      }
      else
      {                          /* nested file; return to previous */
         fclose(threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth]);
         threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth] = NULL;
         if (threadControlBlockPtr->currentInputDepth == 0)
         {                       /* eof with no prev; print error and exit */
            DriverPrintError("\n*** EOF on input file; exiting\n");
            BeginAPICall();
            ARTermination(GetControlStructPtr(), &status);
            EndAPICall(&status);
            exit(1);
         }
         --threadControlBlockPtr->currentInputDepth;
         GetInputLine();
         return;
      }
   }
                                 /* record command if recording active */
   if (threadControlBlockPtr->recordFile != NULL)
   {
      if ((strncmp(threadControlBlockPtr->buffer, commands[COMMAND_STOP_RECORD],
                   strlen(commands[COMMAND_STOP_RECORD])) != 0) ||
          (threadControlBlockPtr->buffer[strlen(commands[COMMAND_STOP_RECORD])] != '\n'))
         fprintf(threadControlBlockPtr->recordFile, "%s",
                 threadControlBlockPtr->buffer);
   }
                                 /* check for full input buffer */
   bufLen = strlen(threadControlBlockPtr->buffer);
   if (bufLen == DRIVER_INPUT_BUFFER_LEN - 1)
   {
      DriverPrintError("   ***  Warning:  Input line length is equal to or exceeds maximum allowed length of %u\n",
                       DRIVER_INPUT_BUFFER_LEN - 1);
   }
                                 /* remove any trailing whitespace */
   bufOffset = bufLen - 1;
   while ((bufOffset >= 0) && ((threadControlBlockPtr->buffer[bufOffset] == ' ') ||
          (threadControlBlockPtr->buffer[bufOffset] == '\t') ||
          (threadControlBlockPtr->buffer[bufOffset] == '\n')))
      bufOffset--;
   threadControlBlockPtr->buffer[bufOffset + 1] = '\0';
                                 /* process if a comment */
   if (threadControlBlockPtr->buffer[0] == '#')
   {
                                 /* if output redirected, echo comment */
                                 /* in the output file                 */
      if (ProcessCommentLine(threadControlBlockPtr->buffer) &&
          threadControlBlockPtr->outFile != stdout)
         DriverPrintResult("%s\n", threadControlBlockPtr->buffer);
   }
}


/*****************************************************************************/
/*                                                                           */
/*                               OpenInputFile                               */
/*                                                                           */
/*****************************************************************************/

int OpenInputFile(inputFilename, exitOnError)
char      *inputFilename;
ARBoolean exitOnError;
{
   char                *filename; /* pointer to filename for input file */
   FILE                *tempFile; /* temporary pointer to new input file */
   ThreadControlBlock  *threadControlBlockPtr; /* control block pointer */

                                 /* get the filename of the new input file */
   if (inputFilename)
      filename = inputFilename;
   else
      filename = GetChar("Filename of input file (): ", "");
   if (strlen(filename) == 0)
      DriverPrintWarning(" **** No filename specified so no change to input file\n");
   else
   {
      threadControlBlockPtr = GetThreadControlBlockPtr();

      if (threadControlBlockPtr->currentInputDepth == MAX_NESTED_INPUT_DEPTH - 1)
      {                          /* print error and return */
         DriverPrintError("\n*** Maximum nested input depth exceeded\n");
         return AR_RETURN_ERROR;
      }
                                 /* open the new file for reading */
      tempFile = fopen(filename, "r");
      if (tempFile == NULL)
      {
         gSavedExitCode = -1;
         if (exitOnError)
         {
            DriverPrintWarning(" **** File error during open of '%s'\n",filename);
            exit(gSavedExitCode);
         }
         else
            DriverPrintWarning(" **** File error during open of '%s'; no change to input file\n",
                               filename);

      }
      else
      {                          /* record input file pointer */   
         threadControlBlockPtr->inFile[++threadControlBlockPtr->currentInputDepth] = tempFile;
      }
   }

   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                               OpenOutputFile                              */
/*                                                                           */
/*****************************************************************************/

int OpenOutputFile()

{
   char                 filename[AR_MAX_FULL_FILENAME + 1];
   char                *inputValue; /* pointer to filename for output file */
   FILE                *tempFile;   /* temporary pointer to new output file */
   ThreadControlBlock  *threadControlBlockPtr; /* control block pointer */

                                 /* get the filename of the new output file */
   inputValue = GetChar("Filename of output file (): ", "");
   if (strlen(inputValue) == 0)
   {
      DriverPrintWarning(" **** No filename specified so no change to output file\n");
      return AR_RETURN_ERROR;
   }
   else
   {                             /* open the new file for writing */
      if (!(gQuietMode & SUPPRESS_RESULTS))
      {
         if (snprintf(filename, AR_MAX_FULL_FILENAME, "%s%s%s", gResultDir,
                      (gResultDir[0] != '\0') ? "\\" : "", inputValue) < 0)
            filename[AR_MAX_FULL_FILENAME] = '\0';

         tempFile = fopen(filename, "w");
         if (tempFile == NULL)
         {
            DriverPrintWarning(" **** File error during open of '%s'; no change to output file\n",
                               filename);
            return AR_RETURN_ERROR;
         }
         else
         {                       /* if there is a current output file; close it */
            threadControlBlockPtr = GetThreadControlBlockPtr();
            if (threadControlBlockPtr->outFile != stdout)
               fclose(threadControlBlockPtr->outFile);
            if (threadControlBlockPtr->outFileName)
               free(threadControlBlockPtr->outFileName);
            threadControlBlockPtr->outFile = tempFile;
            threadControlBlockPtr->outFileName = malloc(strlen(filename) + 1);
            if (threadControlBlockPtr->outFileName)
               strcpy(threadControlBlockPtr->outFileName, filename);
         }
      }
   }
   
   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                               CloseOutputFile                             */
/*                                                                           */
/*****************************************************************************/

int CloseOutputFile()

{
   ThreadControlBlock  *threadControlBlockPtr; /* control block pointer */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->outFile == stdout)
   {
      DriverPrintWarning(" **** Output to stdout; cannot close stdout file\n");
      return AR_RETURN_ERROR;
   }
   else
   {                             /* close output file */
      fclose(threadControlBlockPtr->outFile);
      threadControlBlockPtr->outFile = stdout;
      if (threadControlBlockPtr->outFileName)
      {
         free(threadControlBlockPtr->outFileName);
         threadControlBlockPtr->outFileName = NULL;
      }
   }

   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                               StartRecording                              */
/*                                                                           */
/*****************************************************************************/

int StartRecording()

{
   char                *filename; /* pointer to filename for record file */
   ThreadControlBlock  *threadControlBlockPtr; /* control block pointer */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->recordFile != NULL)
   {
      DriverPrintWarning(
                " **** Recording already active; stop previous to start new\n");
      return AR_RETURN_ERROR;
   }
   else
   {                             /* get a filename and open for recording */
      filename = GetChar("Filename of record file (): ", "");
      if (strlen(filename) == 0)
      {
         DriverPrintError(" **** No filename specified so no recording started\n");
         return AR_RETURN_ERROR;
      }
      else
      {                          /* open the new file for write */
         threadControlBlockPtr->recordFile = fopen(filename, "w");
         if (threadControlBlockPtr->recordFile == NULL)
         {
             DriverPrintError(
                        " **** File error during open; no recording started\n");
            return AR_RETURN_ERROR;
         }

      }
   }

   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                                StopRecording                              */
/*                                                                           */
/*****************************************************************************/

void StopRecording()

{
   ThreadControlBlock  *threadControlBlockPtr; /* control block pointer */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->recordFile == NULL)
      DriverPrintWarning(" **** Recording is not active\n");
   else
   {                             /* close record file */
      fclose(threadControlBlockPtr->recordFile);
      threadControlBlockPtr->recordFile = NULL;
   }
}


/*****************************************************************************/
/*                                                                           */
/*                                  BeginLoop                                */
/*                                                                           */
/*****************************************************************************/

int BeginLoop()

{
   long                 numIterations;
   ThreadControlBlock  *threadControlBlockPtr; /* control block pointer */

   DriverPrintHeader("BEGIN LOOP");

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth] == stdin)
   {                             /* print error and return */
      DriverPrintError("\n*** Command must be issued from input file\n");
      return AR_RETURN_ERROR;
   }

   numIterations = GetLong("Number of iterations (1): ", 1);

   if (threadControlBlockPtr->currentLoopDepth == MAX_NESTED_LOOP_DEPTH - 1)
   {                             /* print error and return */
      DriverPrintError("\n*** Maximum nested loop depth exceeded\n");
      threadControlBlockPtr->numFailedBeginLoop++;
      return AR_RETURN_ERROR;
   }

   threadControlBlockPtr->currentLoopDepth++;

   threadControlBlockPtr->numIterations[threadControlBlockPtr->currentLoopDepth] =
      (numIterations <= 0) ? 1 : (unsigned int) numIterations;

   threadControlBlockPtr->loopBeginFilePos[threadControlBlockPtr->currentLoopDepth] =
      ftell(threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth]);  

   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                                   EndLoop                                 */
/*                                                                           */
/*****************************************************************************/

int EndLoop()

{
   ThreadControlBlock  *threadControlBlockPtr; /* control block pointer */

   DriverPrintHeader("END LOOP");

   threadControlBlockPtr = GetThreadControlBlockPtr();

   if (threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth] == stdin)
   {                             /* print error and return */
      DriverPrintError("\n*** Command must be issued from input file\n");
      return AR_RETURN_ERROR;
   }

   if (threadControlBlockPtr->currentLoopDepth < 0)
   {                             /* print warning and return */
      DriverPrintWarning("\n*** Command ignored because no corresponding begin loop issued\n");
      return AR_RETURN_ERROR;
   }

   if (threadControlBlockPtr->numFailedBeginLoop > 0)
   {                             /* print warning and return */
      DriverPrintWarning("\n*** Command ignored because previous begin loop failed\n");
      threadControlBlockPtr->numFailedBeginLoop--;
      return AR_RETURN_ERROR;
   }

   if (--threadControlBlockPtr->numIterations[threadControlBlockPtr->currentLoopDepth] > 0)
      fseek(threadControlBlockPtr->inFile[threadControlBlockPtr->currentInputDepth],
            threadControlBlockPtr->loopBeginFilePos[threadControlBlockPtr->currentLoopDepth], SEEK_SET);
   else
      threadControlBlockPtr->currentLoopDepth--;

  return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                                DriverSleep                                */
/*                                                                           */
/*****************************************************************************/

void DriverSleep(
unsigned int   seconds
)

{
#ifdef _WIN32
   Sleep((DWORD) (seconds * 1000));
#else
   sleep(seconds);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                              DriverSleepMsec                              */
/*                                                                           */
/*****************************************************************************/

void DriverSleepMsec(
unsigned int   milliseconds
)

{
#ifdef _WIN32
   Sleep((DWORD) milliseconds);
#else
   struct pollfd fds[1] = {{0,}};
   fds[0].fd = -1; /* ignore fd, we only want timer effect */
   poll(fds, 1, milliseconds);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                      DriverInitializeCriticalSection                      */
/*                                                                           */
/*****************************************************************************/

void DriverInitializeCriticalSection(
OS_CRITICAL_SECTION_PTR  criticalSectionPtr
)

{
#ifdef _WIN32
   InitializeCriticalSection(criticalSectionPtr);
#else
   pthread_mutex_init(criticalSectionPtr, NULL);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                         DriverEnterCriticalSection                        */
/*                                                                           */
/*****************************************************************************/

void DriverEnterCriticalSection(
OS_CRITICAL_SECTION_PTR  criticalSectionPtr
)

{
#ifdef _WIN32
   EnterCriticalSection(criticalSectionPtr);
#else
   pthread_mutex_lock(criticalSectionPtr);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                         DriverLeaveCriticalSection                        */
/*                                                                           */
/*****************************************************************************/

void DriverLeaveCriticalSection(
OS_CRITICAL_SECTION_PTR  criticalSectionPtr
)

{
#ifdef _WIN32
   LeaveCriticalSection(criticalSectionPtr);
#else
   pthread_mutex_unlock(criticalSectionPtr);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                        DriverDeleteCriticalSection                        */
/*                                                                           */
/*****************************************************************************/

void DriverDeleteCriticalSection(
OS_CRITICAL_SECTION_PTR  criticalSectionPtr
)

{
#ifdef _WIN32
   DeleteCriticalSection(criticalSectionPtr);
#else
   pthread_mutex_destroy(criticalSectionPtr);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                               DriverTLSAlloc                              */
/*                                                                           */
/*****************************************************************************/

void DriverTLSAlloc(
OS_TLS_OBJECT_PTR  tlsObjectPtr
)

{
#ifdef _WIN32
   *tlsObjectPtr = TlsAlloc();
#else
   pthread_key_create(tlsObjectPtr, NULL);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                             DriverTLSGetValue                             */
/*                                                                           */
/*****************************************************************************/

void * DriverTLSGetValue(
OS_TLS_OBJECT  tlsObject
)

{
#ifdef _WIN32
   return(TlsGetValue(tlsObject));
#else
   return(pthread_getspecific(tlsObject));
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                             DriverTLSSetValue                             */
/*                                                                           */
/*****************************************************************************/

void DriverTLSSetValue(
OS_TLS_OBJECT   tlsObject,
void           *tlsValue
)

{
#ifdef _WIN32
   TlsSetValue(tlsObject, tlsValue);
#else
   pthread_setspecific(tlsObject, tlsValue);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                               DriverTLSFree                               */
/*                                                                           */
/*****************************************************************************/

void DriverTLSFree(
OS_TLS_OBJECT   tlsObject
)

{
#ifdef _WIN32
   TlsFree(tlsObject);
#else
   pthread_key_delete(tlsObject);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                              DriverCreateEvent                            */
/*                                                                           */
/*****************************************************************************/

void DriverCreateEvent(
OS_EVENT_OBJECT  *eventObject,
unsigned int      releaseType
)

{
   eventObject->releaseType = releaseType;

#ifdef _WIN32
   if ((eventObject->eventHandle = CreateEvent(NULL,
                                               (releaseType == RELEASE_ALL)
                                               ? TRUE : FALSE, FALSE,
                                               NULL)) == NULL)
   {
      DriverPrintError(" **** unable to create an event\n");
   }
#else
   pthread_mutex_init(&eventObject->eventMutex, NULL);
   pthread_cond_init(&eventObject->eventCondVar, NULL); 
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                              DriverLockEvent                              */
/*                                                                           */
/*****************************************************************************/

void DriverLockEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifndef _WIN32
   pthread_mutex_lock(&eventObject->eventMutex);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                               DriverSetEvent                              */
/*                                                                           */
/*****************************************************************************/

void DriverSetEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifdef _WIN32
   if (eventObject->releaseType == RELEASE_ALL)
      PulseEvent(eventObject->eventHandle);
   else
      SetEvent(eventObject->eventHandle);
#else
   pthread_mutex_lock(&eventObject->eventMutex);
   if (eventObject->releaseType == RELEASE_ALL)
      pthread_cond_broadcast(&eventObject->eventCondVar);
   else
      pthread_cond_signal(&eventObject->eventCondVar);
   pthread_mutex_unlock(&eventObject->eventMutex); 
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                             DriverWaitForEvent                            */
/*                                                                           */
/*****************************************************************************/

void DriverWaitForEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifdef _WIN32
   WaitForSingleObject(eventObject->eventHandle, INFINITE);
#else
   pthread_cond_wait(&eventObject->eventCondVar,
                     &eventObject->eventMutex);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                             DriverUnlockEvent                             */
/*                                                                           */
/*****************************************************************************/

void DriverUnlockEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifndef _WIN32
   pthread_mutex_unlock(&eventObject->eventMutex);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                              DriverDeleteEvent                            */
/*                                                                           */
/*****************************************************************************/

void DriverDeleteEvent(
OS_EVENT_OBJECT  *eventObject
)

{
#ifdef _WIN32
   CloseHandle(eventObject->eventHandle);
#else
   pthread_mutex_destroy(&eventObject->eventMutex);
   pthread_cond_destroy(&eventObject->eventCondVar);
#endif /* _WIN32 */
}


/*****************************************************************************/
/*                                                                           */
/*                            ReleaseWaitingThreads                          */
/*                                                                           */
/*****************************************************************************/

void ReleaseWaitingThreads()

{
   ThreadControlBlock  *threadControlBlockPtr; /* control block pointer */

   DriverPrintHeader("RELEASE WAITING THREADS");

   threadControlBlockPtr = GetThreadControlBlockPtr();

#ifdef _WIN32
   /* pause for a second to allow for all launched threads to be */
   /* waiting for the release event                              */
   
   DriverSleep(1);
#endif /* _WIN32 */

   /* release all threads waiting on this particular event */

   DriverSetEvent(&threadControlBlockPtr->releaseWaitEvent);
}


/*****************************************************************************/
/*                                                                           */
/*                           MillisecondSleepTimer                           */
/*                                                                           */
/*****************************************************************************/

void MillisecondSleepTimer()

{
   ARStatusList   dummyStatus;
   unsigned int   sleepMilliseconds; /* number of milliseconds to sleep */

   DriverPrintHeader("MILLISECOND SLEEP TIMER");

   dummyStatus.numItems = 0;

   sleepMilliseconds = (unsigned int) GetLong("Number of milliseconds (0): ", 0);

   DriverPrintResult("\n   Sleeping for %u millisecond(s) . . .\n", sleepMilliseconds);
   
   BeginAPICall();
   DriverSleepMsec(sleepMilliseconds);
   EndAPICall(&dummyStatus);
}


/*****************************************************************************/
/*                                                                           */
/*                                SleepTimer                                 */
/*                                                                           */
/*****************************************************************************/

void SleepTimer()

{
   ARStatusList   dummyStatus;
   unsigned int   sleepSeconds; /* number of seconds to sleep */

   DriverPrintHeader("SLEEP TIMER");

   dummyStatus.numItems = 0;

   sleepSeconds = (unsigned int) GetLong("Number of seconds (0): ", 0);

   DriverPrintResult("\n   Sleeping for %u second(s) . . .\n", sleepSeconds);
   
   BeginAPICall();
   DriverSleep(sleepSeconds);
   EndAPICall(&dummyStatus);
}


/*****************************************************************************/
/*                                                                           */
/*                                RandomSleep                                */
/*                                                                           */
/*****************************************************************************/

void RandomSleep(
unsigned int   lowerBound,
unsigned int   upperBound
)

{
   ARStatusList   dummyStatus;  /* dummy status array */
   unsigned int   randomValue;  /* generated random number 0 to RAND_MAX */
   unsigned int   sleepSeconds; /* number of seconds to sleep */

   dummyStatus.numItems = 0;

   /* synchronize the operation so that only one thread can be asking for */
   /* a random number at any one time                                     */

   DriverEnterCriticalSection(&gRandomNumberCriticalSection);

   DriverLockEvent(&gRandomNumberReplyEvent);

   DriverSetEvent(&gRandomNumberRequestEvent);

   DriverWaitForEvent(&gRandomNumberReplyEvent);

   randomValue = gRandomNumberValue;

   DriverUnlockEvent(&gRandomNumberReplyEvent);

   DriverLeaveCriticalSection(&gRandomNumberCriticalSection);

#if RAND_MAX-0 < 32768
   sleepSeconds = lowerBound + (randomValue * (upperBound - lowerBound) +
                                ((RAND_MAX + 1) / 2)) / (RAND_MAX + 1);
#else /* linux */
   sleepSeconds = lowerBound + (unsigned int)
      ((double) randomValue * (upperBound-lowerBound) /
         ((double) RAND_MAX + 1) + 0.5);
#endif

   DriverPrintResult("\n   Sleeping for %u second(s) . . .\n", sleepSeconds);

   BeginAPICall();
   DriverSleep(sleepSeconds);
   EndAPICall(&dummyStatus);
}


/*****************************************************************************/
/*                                                                           */
/*                             RandomSleepTimer                              */
/*                                                                           */
/*****************************************************************************/

int RandomSleepTimer()

{
   unsigned int   lowerBound;   /* lower bound of acceptable range */
   unsigned int   upperBound;   /* upper bound of acceptable range */

   DriverPrintHeader("RANDOM SLEEP TIMER");

   lowerBound = (unsigned long) GetLong("Lower Bound (0): ", 0);
   upperBound = (unsigned long) GetLong("Upper Bound (60): ", 60);

   if (upperBound < lowerBound)
   {
      DriverPrintError(" **** upper bound is less than lower bound\n");
      return AR_RETURN_ERROR;
   }

   RandomSleep(lowerBound, upperBound);
   
   return AR_RETURN_OK;
}


/*****************************************************************************/
/*                                                                           */
/*                         CreateThreadControlBlock                          */
/*                                                                           */
/*****************************************************************************/

void * CreateThreadControlBlock()

{
   ThreadControlBlock  *threadControlBlockPtr;

   if ((threadControlBlockPtr =
         (ThreadControlBlock *) malloc(sizeof(ThreadControlBlock))) == NULL)
   {
      DriverPrintError(" **** malloc error creating thread control block\n");
   }
   else
   {
      /* initialize the thread control block noting that all items starting */
      /* with some form of zero will not be initialized individually        */

      memset((char *) threadControlBlockPtr, 0, sizeof(ThreadControlBlock));

      threadControlBlockPtr->currentLoopDepth = -1;

      /* save the control block pointer in the appropriate location and */
      /* create the necessary synchronization objects                   */

      DriverTLSSetValue(gTLSKey, (void *) threadControlBlockPtr);

      DriverCreateEvent(&threadControlBlockPtr->launchWaitEvent, RELEASE_ONE);
      DriverCreateEvent(&threadControlBlockPtr->releaseWaitEvent, RELEASE_ALL);
   }

   return ((void *) threadControlBlockPtr);
}


/*****************************************************************************/
/*                                                                           */
/*                         GetThreadControlBlockPtr                          */
/*                                                                           */
/*****************************************************************************/

void * GetThreadControlBlockPtr()

{
   /* get the control block pointer from the appropriate location */

   return (DriverTLSGetValue(gTLSKey));
}


/*****************************************************************************/
/*                                                                           */
/*                         DestroyThreadControlBlock                         */
/*                                                                           */
/*****************************************************************************/

void DestroyThreadControlBlock()

{
   unsigned int         i;
   ThreadControlBlock  *threadControlBlockPtr;

   /* get the control block pointer for the current thread */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* close any files that need closing */

   for (i = 1; i <= threadControlBlockPtr->currentInputDepth; i++)
   {
      if (threadControlBlockPtr->inFile[i] != NULL)
         fclose(threadControlBlockPtr->inFile[i]);
   }

   if (threadControlBlockPtr->inFile[0] != stdin)
      fclose(threadControlBlockPtr->inFile[0]);

   if (threadControlBlockPtr->outFile != stdout)
      fclose(threadControlBlockPtr->outFile);

   if (threadControlBlockPtr->recordFile != NULL)
      fclose(threadControlBlockPtr->recordFile);

   ProcessResultFile();

   if (threadControlBlockPtr->resultFile != NULL)
      fclose(threadControlBlockPtr->resultFile);

   /* free any memory that needs freeing */

   if (threadControlBlockPtr->outFileName != NULL)
      free(threadControlBlockPtr->outFileName);

   /* wait until all threads launched by this thread have completed */

   if (threadControlBlockPtr->numHandles > 0)
   {
#ifdef _WIN32
      /* since we can only wait on a limited number of handles we will */
      /* potentially have to wait multiple times                       */

      unsigned int numHandlesProcessed = 0;

      while (numHandlesProcessed < threadControlBlockPtr->numHandles)
      {
         WaitForMultipleObjects(min(MAXIMUM_WAIT_OBJECTS,
                                    threadControlBlockPtr->numHandles -
                                     numHandlesProcessed),
                                &threadControlBlockPtr->
                                 threadHandles[numHandlesProcessed],
                                TRUE, INFINITE);

         numHandlesProcessed += MAXIMUM_WAIT_OBJECTS;
      }
#else
      for (i = 0; i < threadControlBlockPtr->numHandles; i++)
         pthread_join(threadControlBlockPtr->threadHandles[i], NULL);
#endif /* _WIN32 */
   }

   /* close the thread handles now that we know the threads have terminated */

   for (i = 0; i < threadControlBlockPtr->numHandles; i++)
   {
#ifdef _WIN32
      CloseHandle(threadControlBlockPtr->threadHandles[i]);
#else
      pthread_detach(threadControlBlockPtr->threadHandles[i]);
#endif /* _WIN32 */
   }

   /* clean up the event objects */

   DriverDeleteEvent(&threadControlBlockPtr->launchWaitEvent);
   DriverDeleteEvent(&threadControlBlockPtr->releaseWaitEvent);

   if (threadControlBlockPtr->threadHandles)
      free(threadControlBlockPtr->threadHandles);

   free(threadControlBlockPtr);
}


/*****************************************************************************/
/*                                                                           */
/*                            GetControlStructPtr                            */
/*                                                                           */
/*****************************************************************************/

ARControlStruct * GetControlStructPtr()

{
   return(&((ThreadControlBlock *) GetThreadControlBlockPtr())->control);
}


/*****************************************************************************/
/*                                                                           */
/*                          SaveFirstSchemaListName                          */
/*                                                                           */
/*****************************************************************************/

void SaveFirstSchemaListName(schemaList)
ARNameList *schemaList;     /* IN; contains schema name to save */
{
   ThreadControlBlock  *threadControlBlockPtr;

   /* get the control block pointer for the current thread */
   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* clear out any existing schema names */
   threadControlBlockPtr->firstSchemaListName[0] = '\0';

   /* set the first schema name to the appropriate values */
   if (schemaList->numItems > 0)
      strcpy(threadControlBlockPtr->firstSchemaListName,
             schemaList->nameList[0]);
}


/*****************************************************************************/
/*                                                                           */
/*                             SaveCreateEntryId                             */
/*                                                                           */
/*****************************************************************************/

void SaveCreateEntryId(entryId)
AREntryIdType   entryId;     /* IN; entry id to save */
{
   ThreadControlBlock  *threadControlBlockPtr;

   /* get the control block pointer for the current thread */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* set the entry id to the supplied value */

   strcpy(threadControlBlockPtr->createEntryId, entryId);
}


/*****************************************************************************/
/*                                                                           */
/*                           SaveEntryListEntryIds                           */
/*                                                                           */
/*****************************************************************************/

void SaveEntryListEntryIds(entryListList)
AREntryListList  *entryListList; /* IN; entry ids to save */
{
   unsigned int         i;
   AREntryListStruct   *tempPtr;
   ThreadControlBlock  *threadControlBlockPtr;

   /* get the control block pointer for the current thread */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* clear out any existing entry ids */

   for (i = 0; i < MAX_JOIN_ENTRY_IDS; i++)
   {
      threadControlBlockPtr->firstGLEListId[i][0] = '\0';
      threadControlBlockPtr->secondGLEListId[i][0] = '\0';
      threadControlBlockPtr->lastGLEListId[i][0] = '\0';
   }

   /* set the entry ids to the appropriate values */

   if (entryListList->numItems > 0)
   {
      tempPtr = entryListList->entryList;

      for (i = 0; (i < MAX_JOIN_ENTRY_IDS && i < tempPtr->entryId.numItems); i++)
      {
         strcpy(threadControlBlockPtr->firstGLEListId[i],
                tempPtr->entryId.entryIdList[i]);
      }
      
      tempPtr = (entryListList->numItems > 1)
                ? &entryListList->entryList[1]
                : entryListList->entryList;

      for (i = 0; (i < MAX_JOIN_ENTRY_IDS && i < tempPtr->entryId.numItems); i++)
      {
         strcpy(threadControlBlockPtr->secondGLEListId[i],
                tempPtr->entryId.entryIdList[i]);
      }

      tempPtr = &entryListList->entryList[entryListList->numItems - 1];

      for (i = 0; (i < MAX_JOIN_ENTRY_IDS && i < tempPtr->entryId.numItems); i++)
      {
         strcpy(threadControlBlockPtr->lastGLEListId[i],
                tempPtr->entryId.entryIdList[i]);
      }
   }
}


/*****************************************************************************/
/*                                                                           */
/*                      SaveEntryListFieldValueEntryIds                      */
/*                                                                           */
/*****************************************************************************/

void SaveEntryListFieldValueEntryIds(entryListFieldValueList)
AREntryListFieldValueList  *entryListFieldValueList; /* IN; entry ids to save */
{
   unsigned int                  i;
   AREntryListFieldValueStruct  *tempPtr;
   ThreadControlBlock           *threadControlBlockPtr;

   /* get the control block pointer for the current thread */

   threadControlBlockPtr = GetThreadControlBlockPtr();

   /* clear out any existing entry ids */

   for (i = 0; i < MAX_JOIN_ENTRY_IDS; i++)
   {
      threadControlBlockPtr->firstGLEWFListId[i][0] = '\0';
      threadControlBlockPtr->secondGLEWFListId[i][0] = '\0';
      threadControlBlockPtr->lastGLEWFListId[i][0] = '\0';
   }

   /* set the entry ids to the appropriate values */

   if (entryListFieldValueList->numItems > 0)
   {
      tempPtr = entryListFieldValueList->entryList;

      for (i = 0; (i < MAX_JOIN_ENTRY_IDS && i < tempPtr->entryId.numItems); i++)
      {
         strcpy(threadControlBlockPtr->firstGLEWFListId[i],
                tempPtr->entryId.entryIdList[i]);
      }
      
      tempPtr = (entryListFieldValueList->numItems > 1)
                ? &entryListFieldValueList->entryList[1]
                : entryListFieldValueList->entryList;

      for (i = 0; (i < MAX_JOIN_ENTRY_IDS && i < tempPtr->entryId.numItems); i++)
      {
         strcpy(threadControlBlockPtr->secondGLEWFListId[i],
                tempPtr->entryId.entryIdList[i]);
      }

      tempPtr = &entryListFieldValueList->entryList[entryListFieldValueList->numItems - 1];

      for (i = 0; (i < MAX_JOIN_ENTRY_IDS && i < tempPtr->entryId.numItems); i++)
      {
         strcpy(threadControlBlockPtr->lastGLEWFListId[i],
                tempPtr->entryId.entryIdList[i]);
      }
   }
}


/*****************************************************************************/
/*                                                                           */
/*                                GetJDValue                                 */
/*                                                                           */
/*****************************************************************************/

int GetJDValue(
char  *dateStr,     /* IN: date string in ISO-8601 format [-]YYYY-MM-DD */
int   *jd           /* OUT: julian date number of input date */
)
{
   ARDateStruct   date;
   char          *dateStrPtr;
   int            errorCode;
   ARStatusList   status;

   errorCode = AR_ERROR_NONE;
   dateStrPtr = dateStr;

   /*
    * Perform basic date format check.
    */
   /* Skip negative sign if present. */
   if (*dateStrPtr == '-') dateStrPtr++;

   /* Check year. */
   if (!isdigit(0xff & *dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(0xff & *dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(0xff & *dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(0xff & *dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (*dateStrPtr++ != '-')    return AR_ERROR_INVALID_DATE_FORMAT;

   /* Check month. */ 
   if (!isdigit(0xff & *dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(0xff & *dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (*dateStrPtr++ != '-')    return AR_ERROR_INVALID_DATE_FORMAT;
   
   /* Check day. */
   if (!isdigit(0xff & *dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;
   if (!isdigit(0xff & *dateStrPtr++)) return AR_ERROR_INVALID_DATE_FORMAT;


   date.year  = atoi(dateStr);
   date.month = atoi(dateStr + 5);
   date.day   = atoi(dateStr + 8);

   ARDateToJulianDate(NULL, &date, jd, &status);

   if (status.numItems > 0 &&
       status.statusList[0].messageType > AR_RETURN_WARNING)
   {
      errorCode = status.statusList[0].messageNum;
      FreeARStatusList(&status, FALSE);
   }

   return errorCode;
}


/*****************************************************************************/
/*                                                                           */
/*                               GetDateString                               */
/*                                                                           */
/*****************************************************************************/

int GetDateString(
int    jd,          /* IN: julian date number to convert */
char  *dateStr      /* OUT: date string in ISO-8601 format [-]YYYY-MM-DD */
)

{
   ARDateStruct   date;
   int            errorCode;
   ARStatusList   status;

   errorCode = AR_ERROR_NONE;

   ARJulianDateToDate(NULL, jd, &date, &status);

   sprintf(dateStr, "%.4d-%.2d-%.2d", date.year, date.month, date.day);

   if (status.numItems > 0 &&
       status.statusList[0].messageType > AR_RETURN_WARNING)
   {
      errorCode = status.statusList[0].messageNum;
      FreeARStatusList(&status, FALSE);
   }

   return errorCode;
}
/*****************************************************************************/
/*                                                                           */
/*                               GetTODString                                */
/*                                                                           */
/*****************************************************************************/

   void 
GetTODString(
   int    tod,         /* IN: Time of day in seconds */
   char  *todStr )     /* OUT: TOD string in HH:MM:SS A/PM format */
{
   int            safeTod, hour, min, sec;

   safeTod = tod % (3600 * 24);
   hour = safeTod / 3600;
   min = (safeTod % 3600) / 60;
   sec = (safeTod % 3600) % 60;

   sprintf(todStr, "%.2d:%.2d:%.2d %s", 
      hour > 12 ? hour - 12 : hour,
      min, sec,
      hour > 12 ? "PM" : "AM" );
}

